package spacewar;

import java.awt.Menu;
import java.awt.CheckboxMenuItem;
import java.awt.Frame;
import java.awt.TextArea;
import java.awt.Dimension;
  
/**
 * This aspect specifies debugging information to be output to the
 * information window.
 *
 * When the debug aspect is compiled in the Frame menu has several checkbox
 * items that can be used to control the amount of tracing information
 * displayed.  (By default the first three are off, because they generate
 * so much information.)
 *
 * There are two reasons to gather all this debugging code into an aspect
 * like this:
 *
 *   (1) It makes it easier to understand when it is all in one place.
 *
 *   (2) It means that we can "plug and debug".  We can enable/disable
 *       the debugging code simply by weaving or not weaving this
 *       aspect in.
 *
 * All in all, this is a lot better than the usual practice of writing
 * complex debugging code and then deleting it when the bug is found,
 * only to regret it a month later when a related bug surfaces.  (Or even
 * the same bug!)
 *
 * This file also defines a class InfoWin, which it uses to display all the
 * debugging information.
 */
aspect Debug {

    private static InfoWin infoWin = new InfoWin();

    private static Menu menu = new Menu("Debug");

    private static CheckboxMenuItem traceConstructors =
        new CheckboxMenuItem("trace constructors", false);
    private static CheckboxMenuItem traceInitializations =
        new CheckboxMenuItem("trace initializations", false);
    private static CheckboxMenuItem traceMethods =
        new CheckboxMenuItem("trace methods", false);
    private static CheckboxMenuItem traceClockTick =
        new CheckboxMenuItem("trace clock tick", false);
    private static CheckboxMenuItem traceRegistry =
        new CheckboxMenuItem("trace registry", true);
    private static CheckboxMenuItem traceFireCollideDamage =
        new CheckboxMenuItem("trace fire, collide, damage", true);

    after() returning (SWFrame frame): call(SWFrame+.new(..)) {
        menu.add(traceConstructors);
        menu.add(traceInitializations);
        menu.add(traceMethods);
        menu.add(traceClockTick);
        menu.add(traceRegistry);
        menu.add(traceFireCollideDamage);
        frame.getMenuBar().add(menu);
    }

    /*
     * all constructors
     */
    pointcut allConstructorsCut(): 
	call((spacewar.* && !(Debug+ || InfoWin+)).new(..));

    before(): allConstructorsCut() {
        if (traceConstructors.getState()) {
            infoWin.println("begin constructing " + thisJoinPoint.getSignature());
        }
    }

    after() returning: allConstructorsCut() {
        if (traceConstructors.getState()) {
            infoWin.println("done constructing " + thisJoinPoint.getSignature());
        }
    }

    /*
     * All dynamic initializations
     */
    pointcut allInitializationsCut(): 
	initialization((spacewar.* && !(Debug+ || InfoWin+)).new(..));

    before(): allInitializationsCut() {
        if (traceConstructors.getState()) {
            infoWin.println("begin initializing " + thisJoinPoint.getSignature());
        }
    }
    after() returning : allInitializationsCut() {
        if (traceConstructors.getState()) {
            infoWin.println("done initializing " + thisJoinPoint.getSignature());
        }
    }

    /*
     * all methods
     */
    pointcut allMethodsCut(): 
        execution(* (spacewar.* && !(Debug+ || InfoWin+)).*(..));

    before(): allMethodsCut() {
        if (traceMethods.getState()) {
            infoWin.println("entering " + thisJoinPoint.getSignature());
        }
    }
    after() returning : allMethodsCut() {
        if (traceMethods.getState()) {
            infoWin.println("exiting " + thisJoinPoint.getSignature());
        }
    }

    /*
     * clock ticks
     */
    after(Object obj) returning :
        (target(obj) && (target(Game) ||
			 target(Registry) ||
			 target(SpaceObject)))
        && call(void clockTick()) {
        if (traceClockTick.getState())
            infoWin.println("ticking " + obj);
    }

    /*
     * registry contents
     */
    after(Registry registry) returning :
        target(registry) && (call(void register(..)) ||
			     call(void unregister(..))) {
        if (traceRegistry.getState())
            infoWin.println(registry.getTable().size() +
                            " space objects in the registry.");
    }

    /*
     * fire, collide, damage
     */
    after() returning : call(void Ship.fire()) {
        if (traceFireCollideDamage.getState())
            infoWin.println("firing");
    }

    after(Ship ship, SpaceObject obj) returning :
	call(void Ship.handleCollision(SpaceObject)) && target(ship) && args(obj) {
        if (traceFireCollideDamage.getState())
            infoWin.println(ship + " collides with " + obj);
    }

    after(Ship shipA, Ship shipB) returning :
        execution(void Ship.bounce(Ship, Ship)) && args(shipA, shipB) {
        if (traceFireCollideDamage.getState())
            infoWin.println(shipA + " bounces with " + shipB);
    }

    before(Ship ship, double amount):
	call(void Ship.inflictDamage(double)) && target(ship) && args(amount) {
        if (traceFireCollideDamage.getState())
            if (amount > 0)
                infoWin.println(ship + "gets " +
                                amount + " damage (" +
                                ship.getDamage() + ")");
    }

}

class InfoWin {
    private Frame    frame;
    private TextArea info;

    InfoWin() {
        frame = new Frame("debugging info for spacewar game");
        info = new TextArea();
        info.setEditable(false);

        Dimension screenSize = frame.getToolkit().getScreenSize();
        frame.setSize(250, 600);
        frame.setLocation(screenSize.width - 250, 0);
        frame.add(info);
        frame.show();
        frame.toFront();
    }

    void clear() {
        info.setText("");
    }

    void println(String line) {
        info.append(line + "\n");
    }

    void print(String line) {
        info.append(line);
    }
}
